Exit Full View

Glok / Comparison with JavaFX.md

Comparison with JavaFX

I'm not trying to create a clone of JavaFX, so expect many differences. When Glok's approach is different from JavaFX, I'll note it here.

Many of these differences are improvements IMHO. That was a pleasant surprise, because I set out to build a good enough alternative to JavaFX. But as I started working on it, I've seen plenty of opportunities for improvements.

Float not Double

JavaFX's coordinate system uses Double, Glok uses Float.

Is there something I'm missing? Float has 7 digits of precision. Easily enough. Isn't it? Displays are thousands of pixels wide, not millions!

Glok doesn't support transformations (e.g. rotation), maybe that's why JavaFX chose Double??? Update. Glok now supports rotations in multiples of 90°.

Event Handling

Glok probably doesn't behave exactly like JavaFX in the order/way events are processed, especially mouse events (there are lots of edge cases).

One node can have multiple event handlers for a single event type. This is only true for some events in JavaFX. For example Button's onAction is a single instance, not a collection. So in JavaFX, there is only 1 onAction handler. In Glok, onAction is a single instance, but that instance can be a chain. When setting onAction, you can choose to replace the existing handler (if there is one), or chain them (with the new handler either before, or after the existing handler).

Drag Events

Glok currently only supports the simple press-drag-release gesture, whereas JavaFX has 3 different drag gestures.

Also, the way that dragging is initiated is different in Glok. We have MouseEvent.capture(), whereas JavaFX seems to detect dragging automatically. I think it checks if a mouse movements is beyond a threshold. If so, there's some edge cases which are be problematic. e.g. If you press the edge of a button and move slightly (less than the threshold), and the mouse is now in another button. Should the small movement issue a mouse move event, what about mouse enter/exit events? It seems fragile.

So, while Glok's solution requires an extra line of application code, IMHO, it is cleaner. Also, when/if Glok implements the other 2 drag gestures available in JavaFX, then capture() will be a good place to state which type to use.

No Skins

Controls are not Skinnable (but are all stylable, using a Theme). I do like the clear separation of model and view when using JavaFX, but I don't see the need for skins. FYI, the Skin is generally much more complicated than the Control class itself. So to create a new Skin is not much easier (if at all) than creating a new Control from scratch.

I've never seen an alternate skin for a built-in JavaFX control. Styling seems to be sufficient (which Glok does support).

All Glok controls extend Region directly, there is no Control class.

The Default Theme

Glok's default theme shamelessly mimics that of Muse Score, a music composition application under a GPL license.

Unlike JavaFX, Glok's themes do not use CSS, they are created using A DSL (domain specific language), which is Kotlin code.

This has many benefits :

  1. Makes full use of your IDE's syntax highlighting, auto-complete etc.

  2. Less error-prone, as syntax errors are highlighted while typing.

  3. Named constants can be used instead of plain strings (even less error-prone).

  4. Much more flexible, because any Kotlin code can be used. e.g. you can create your own function to create a derived color from a base color.

  5. Themes can be changed at run-time very easily. The default theme (Tantalum) uses Properties. If you change any of these properties, the theme will be regenerated. For example, Changing the accent color throughout your application is as easy as :

     Tantalum.accentColor = Color.RED
    

    This is true of other properties, such as padding for Buttons.

    The default theme uses a color palette of 12 colors, which can be switched from light to dark with a single line of code.

ContentDisplay

Glok's default theme sets Buttons in a ToolBar to use ContentDisplay.GRAPHIC_ONLY. This means that a button which has text and a graphic will only display the graphic.

This is important (and different to JavaFX), because we can define buttons with both a graphic and text, and if they are in a ToolBar, the text will be omitted, but in other places the text will be displayed.

The action package relies on this behaviour. e.g. a Save Action creates a button with text AND a graphic, but when that button is placed in a ToolBar, it will only show the graphic, not the "Save" text. If (on very rare occasions) you want a toolbar button to display the text as well as the graphic, set contentDisplay = ContentDisplay.LEFT.

IMHO, JavaFX's contentDisplay = ContentDisplay.GRAPHIC_ONLY is a really neat idea, and is underused within JavaFX because JavaFX's default theme does not set it within ToolBars.

Input Focus and Focus Navigation

Not finished yet.

JavaFX highlights the control with input focus, regardless of how it gained focus (via mouse clicks or via the keyboard). IMHO, this is wrong.

When you click a button, you know you've done it, why do you need a colored border around it? This is visual clutter.

For those who like, or need, to use the keyboard to navigate, additional visual clues are required. This part isn't implemented yet.

I also plan taking navigation ideas from Muse Score too. One shortcut allows you to jump from one "section" of the GUI to another

TextArea

Rather than storing the text as a simple String, Glok's TextArea uses a TextDocument class, which has an ObservableList of Strings. TextDocument fires TextChange events whenever it changes

Advantages :

  1. A TextDocument can be shared by two TextAreas (e.g. use a SplitPane for 2 views of the same TextDocument)
  2. Efficiency. A traditional approach gets bogged down when documents are long. IMHO, 1,000 lines is short ;-)
  3. The caret and anchor positions are sensible, giving a row and column index. A traditional approach only supplies an Int, which is the index from the start of the document. IMHO, this is daft/useless!

Disadvantages :

  1. The text property is a calculation, and therefore slow.
  2. If you want to observe a TextView, you need to observe the ObservableList, or use a DocumentListener.
  3. TextArea and TextField share less in common (the TextInputControl base class).

StyledTextArea

StyledTextArea is very similar to TextArea, but also has the ability to style parts of the document. The styling is limited - all fonts use must be fixed-width and the same size. This still allows for different styles (bold, italics, plain), and also lets you change the font's color as well as the background color. Underlining text can also be achieved using UnderLineBackground. However, you cannot underline, AND change the background color.

Use cases :

  1. Syntax highlighting of source code.
  2. Styled read-only text (as seen in the "About" tab of each Demo).
  3. Highlighting search matches as part of a search and replace tool
  4. Other highlights (e.g. highlight a line which contains an error)

At present, StyledTextArea is not suitable as a rich text editor, because the highlights are not part of the document's undo/redo mechanism.

ScrollPane

Maybe I don't understand all the use-cases for ScrollPane - Glok's implementation is drastically simpler than JavaFX's. There are no external properties for hMin hMax hValue etc. They are calculated based on the viewport size, and the preferred size of the content.

FitToWidth / fitToHeight default to true. I think JavaFX defaults to false (and I'm not sure if the semantics are the same).

Property Beans

In Glok, the bean and beanName are set for you, if you use a Kotlin delegate to create the Property like so :

class MyObject {
    val widthProperty by doubleProperty(1.0)
    var width by widthProperty
    
    init {
        println( widthProperty )
    }
}

The result is :

MyObject.width = 1.0

We can see it's working from the output - Property's toString() prints the bean's simple class name, and the beanName. In this case MyObject and width.

EventType

Is it just me, or were you dumbfounded when you first saw JavaFX's EventType? I remember looking for EventType.MOUSE_PRESSED, and not being able to find it. Glok puts them all in EventType. JavaFX smears them out across many Event classes.

Adding event handlers in Glok is simpler (despite using identical constructs behind the scenes) :

myNode.onMousePressed { println("Hello World") }

But you can still do it the JavaFX way if you prefer.

Event isn't a generic type. There might be cases where this is a disadvantage, but it's fine for the vast majority of the time.

Key Events

There are separate, independent classes for KeyEvent and KeyTypedEvent, as they contain different data.

Mouse Events

The position of the mouse is only given relative to the scene, not relative to the Node handing the event. In the vast majority of cases, we don't care where the mouse is, so IMHO for Glok to do this calculation is wasteful.

To find the local coordinates : event.sceneX - myNode.sceneX, and the same for Y.

However, this won't give the answer you might expect for nodes with transformations. e.g. when using the Rotation Node. Sorry. This is in my bug list, please be patient. It's easy to get around this, on an ad-hoc basis. But a complete (and fast) solution is trickier. And I haven't decided on the best approach yet.

Listeners

JavaFX uses the name addListener for both InvalidationListener and ChangeListener. This is annoying when using lambdas. So Glok uses two different names :

  • addListener for InvalidationListener
  • addChangeListener for ChangeListener / ListChangeListnerer / SetChangeListener.

As with JavaFX, Glok uses strong references for listeners by default, and there are wrapper classes WeakInvalidationListener and WeakChangeListener, which can help prevent memory leaks.

Glok also provides the methods addWeakListener and addWeakChangeListener which is simpler than using the wrapper classes directly.

Generic Property Types

JavaFX has specific subclasses of Property (such as StringProperty) and a generic ObjectProperty<T>. In JavaFX, if you want a Color property, you use ObjectProperty<Color>.

Glok is similar, it has a generic Property<V>, and specifically typed subclasses, such as StringProperty, FloatProperty. However, unlike JavaFX, Glok has specifically typed subclass for all properties. e.g. there is a ColorProperty which extends Property<Color>. Glok applications are encouraged to do the same, but you don't have to.

There is a template file, and a shell script which generates the required boilerplate. Feel free to copy/paste them into your application!

There is a downside to this approach, the API looks scary. The boilerplate package is a monster .

This extra boilerplate lets us use property-functions, that would be impossible otherwise (due to Type-Erasure).

e.g. Consider a SideProperty (values TOP, LEFT, BOTTOM, RIGHT), we can define an extension function, which converts it to an ObservableOrientation (values HORIZONTAL, VERTICAL) :

val sideProperty by sideProperty( Side.LEFT )
var side by sideProperty 

val orientationProperty = sideProperty.orientation()
val orientation by orientationProperty 

FYI The definition of the orientation() method is :

fun ObservableSide.orientation(): ObservableOrientation = OrientationUnaryFunction(this) { it.orientation() }

Changing side automagically changes orientation, and we can listen to orientationProperty for when side changes from TOP/BOTTOM to LEFT/RIGHT or vice-versa.

Grow/Shrink Priority

JavaFX uses an enum class Priority to decide which Nodes should grow when laying out child Nodes.

Glok takes a different approach. Every Node has a growPriority. When the parent Node is larger than required by its children's sizes, then any children with a growPriority > 0 will become larger than their pref size. The extra amount allocated is the ratio of a child's growPriorty to the total of all children's growPriority. For example, if one child has a priority of 1 and the other has a priority of 0.5, then the first child will be given twice as much extra space as its sibling.

A similar thing occurs when the available size is less than required by the children's pref size, but this time shrinkPriority is used.

This, IMHO, gives a more pleasing result than JavaFX's approach. Especially when you want the shrinkPriority to be different to the growPriority (which JavaFX doesn't support).

FYI, For a near identical behaviour to JavaFX :

  • Priority.ALWAYS -> grow and shrinkPriority of 1.0
  • Priority.SOMETIMES -> grow and shrinkPriority of 0.001 (an arbitrary small number)
  • Priority.NEVER -> grow and shrinkPriority of 0

Node.visible is considered during layout

Unlike JavaFX, the Node's visible property is considered when laying out children. So if you make a child invisible, other nodes will move into that space. With JavaFX an invisible Node still occupies space. I can't think of a single time that I've wanted JavaFX's behaviour, and plenty of times that I've wanted Glok's.

This is a very big deal to me, and IMHO, JavaFX took a big step backwards in this regard. Temporarily hiding a button in a ToolBar is really hard in JavaFX!

Collections

Glok uses Kotlin naming convention for List and Set. i.e. ObservableList and ObservableSet are immutable, while MutableObservableList and MutableObservableSet are mutable.

JavaFX uses ReadOnlyObservableList and ObservableList (the latter is mutable).

Unlike JavaFX, Glok only passes a single ListChange to ListChangeListeners. This means that there are multiple events fired for ObservableList.retainAll( Collection ) and ObservableList.removeAll( Collection ). The same applies to ObservableSet.

Collections are in the subproject glok-model, so you can use them without having a dependency on glok-core.

Themes (Styling a Scene)

Glok doesn't use CSS to style scenes. Instead, it has a DSL (Domain Specific Language) to define a Theme. IMHO, this is much nicer to use, and is more flexible.

Here's a snippet from a theme definition :

    val buttons = button or toggleButton or radioButton
    
    buttons {
        borderSize(1)
        plainBorder(borderColor)
        background(buttonColor)
        padding(buttonPadding)
        labelPadding(labelPadding)

        HOVER {
            plainBorder(highlightColor)
        }
        ARMED {
            background(highlightColor)
        }
        // Only use by ToggleButton and RadioButton, but not Button.
        SELECTED { 
            background(panelColor.brightnessFactor(0.8f))
            border(RectangleBorder(borderThickness, highlightColor))
        }
    }

Isn't that cleaner than CSS? This is Kotlin code, so your IDE will auto-complete, syntax highlight etc. Note, that this defines the styles for all the button types, not just Button itself.

BTW, in this snippet buttonColor, labelPadding are the values of Properties. If we change the property value mid-application, the scene will be restyled to reflect that change. So, for example, it is easy to let users choose the highlightColor at runtime.

Oh, BTW, Node.styles is equivalent to JavaFX's Node.styleClasses, but Glok uses an ObservableSet rather than an ObservableList.

Scaling for High DPI Monitors

Scaling for high DPI devices (dots per inch) can be changed at runtime. JavaFX reads the env variable GDK_SCALE only at startup, and cannot change scale at runtime.

Glok does the same at start-up, as well as applying a heuristic based on monitor size if GDK_SCALE is absent. However, it stores the scaling factor as a property on [Application], which you can change mid-application to scale the entire GUI. Nice.

Fonts and images can use the full resolution of your display, while the application uses logical pixels, rather than physical pixels to define everything.

This means your application is easy to code (because it uses logical pixels everywhere), and looks good (because the full resolution of the display is used).

TextField

As well as prefColumnCount, there's also minColumnCount.

It has an expectDigits property, which, when true, uses the max width of digit glyphs (as well as ., -) rather than W when calculating prefWidth and minWidth from prefColumnCount and minColumnCount. Without this (I'm look at you JavaFX), prefColumnCount is useless for number fields! The default value for expectDigits is true for Spinner's editor, but false everywhere else.

TabPane

Glok's TabPane is a simple wrapper around two other independent controls : TabBar and SingleContainer. A TabBar only show the tabs, not the contents of the current tab. By breaking it into two parts, we get greater flexibility.

For example, you can place a TabPane into a ToolBar, and add extra buttons to the ToolBar. e.g. A "Open Recent Files" pull-down, and a "New Tab" button.

It also gives greater flexibility for the layout of your scene. e.g. you could place a ToolBar between the TabBar and the SingleContainer. (I'm not a fan of this design, but many web browsers have their toolBars between the TabBar and the main content).

The API of a TabPane is what your would expect, but it has very little code, which just forwards everything to the TabBar, which does all the hard work.

Glok's default Theme has two different styles for TabBar/TabPane, as there are two very different uses :

  1. Tabs containing your applications documents
  2. Different sections, e.g. Application preferences split over many tabs.

SplitPane

SplitPane currently ignores minWidth/minHeight of its child nodes. For my needs this isn't an issue, but I do plan on respecting minWidth/minHeight at a later date.

Applications which prevent me from shrinking a Docked panel annoy me! (Due to the SplitPane respecting the minWidth of the Dock).

ListView

Only supports vertical lists (and I have no plans to implement horizontal lists).

Currently only supports single-selection model.

Doesn't support editing in place, although custom ListCells can have editable controls (e.g. TextFields rather than Labels).

ListCells are not reused (as they are in JavaFX). As you scroll through a ListView, new ListCells are created as required (and the old ones garbage collected).

IMHO, this makes application code involving custom cells simpler. The Glok code is simpler too.

There is a small performance hit, but it isn't significant.

TreeView

Like ListView, TreeCells are not reused (as they are in JavaFX).

Glok also has a MixedTreeView, which is designed for trees where the items can be of different types.

I've always disliked using JavaFX's TreeView and never knew why until I wrote MixedTreeView ;-)

FYI, TreeView and MixedTreeView look identical, and use the same Theme rules.

Stage and Window

Unlike JavaFX, Stage is not a subclass of Window. There is no public API to get access to Glok's Window.

Window contains OpenGL specific code, nothing that an application should ever need access to.

Glok has two distinct types of Stage

A RegularStage is implemented by a native window, but Glok also has OverlayStage, which are fake windows drawn on top of a RegularStage. There are a few reasons for this.

  1. I couldn't find a way to create a modal window using LWJGL (the library Glok depends on for OpenGL).
  2. When in full-screen mode, you cannot open another window.
  3. If Glok is ever used within a web browser, we cannot open a new window

RegularStage and OverlayStage both implement the Stage interface, so app developers shouldn't care which implementation is used.

Note however, OverlayStage's window decorations won't match the native ones. They are rendered by Glok, not the platform's window manager (and are styled using a Theme, just like all other Glok Nodes).

PopupMenus and ToolTips both use OverlayStage. This has a couple of drawbacks :

  1. PopupMenus cannot extend beyond the bounds of the RegularStage. So if you have a long menu, and a small RegularStage, the menu will be cropped.
  2. If you open a PopupMenu and then click the RegularStage's title bar, the PopupMenu doesn't disappear. Glok doesn't receive that mouse click event, so there's nothing I can do about it :-(

Menus

PopupMenu can contain ANY node types, and MenuItem is just a Node. i.e. there is no special hierarchy for menu items.

A few advantages :

  1. Less Glok code (as MenuItem is just another subclass of Node)
  2. More flexible
  3. Menus can be styled using Themes, just like any other Nodes.

Command Line Arguments

IMHO, JavaFX makes a pig's ear of handling command line arguments.

Firstly, it calls them Parameters - Grr - Parameters don't have values, an argument is the value that is supplied to a particular parameter. So the data structure which holds values, should be called Arguments.

Secondly, JavaFX has built-in argument parsing, but doesn't follow the Posix standard of parsing -- as the end of arguments. Grr.

Glok doesn't automatically attempt to parse arguments at start-up (which is impossible without knowing the allowable options, and which options have values, and which are just flags).

However, there are two built-in argument parsers which your application can call if you wish :

  • fun posixArguments( ... ). Uses single dashes for flags and options expecting values. e.g. -foo value
  • fun doubleDashArguments( ... ). Uses single dashes for single-character flags, and two dashes for longer flag/parameter names. The value is always preceded by a space. e.g. --foo value.

I considered writing a GNU-style version which uses --foo=value syntax, but it's so annoying to use, due to the problem with filename completion.

Feel free to ignore both implementations, and parse the arguments as you see fit.